import {Word, Dictionary, OpCode} from "./interfaces.ts";
export type {Word, Dictionary};
export {OpCode};
import {mapdict} from './mapdict.ts';
import {CompositeDictionary} from './composite-dictionary.ts';
import * as hex from 'https://deno.land/std/encoding/hex.ts';
async function noop() {
}
class Script implements Word {
    constructor(private script:string) {
    }
    async expand(dict:Dictionary):Promise<string[]> {
        var loops:any = mapdict();
        var dictionary = new CompositeDictionary([dict, loops]);
        var words = this.script.match(/"[^"]*"|\S+/g);
        if (words == null) return [];
        var expanded:string[] = [];
        var scriptFetcher = {
            async fetch(word:string) {
                if (word=='(') {
                    return commentFetcher;
                }
                if (word==':') {
                    return wordDefFetcher;
                }
                var matchedString = word.match(/^\"([^\"]*)\"$/);
                if (matchedString) {
                    const encoder = new TextEncoder()
                    const buffer = encoder.encode(matchedString[1]);

                    expanded.push('0x'+hex.encodeToString(buffer));
                    return;
                }
                if (word.match(/^0x.*$/)||word.match(/^[+-]?\d+(\.\d+)?$/)) {
                    expanded.push(word);
                    return;
                }
                var matched = word.match(/^loop\((\d+)\,([^\)]*)\)$/);
                if (matched) {
                    var script = new Script(matched[2]);
                    for (var i=0;i<Number(matched[1]);i++) {
                        loops['#'+matched[2]] = new OpCode(''+i);
                        expanded = expanded.concat(await script.expand(dictionary));
                    }
                    return;
                }
                var found = await dictionary.lookup(word);
                if (found) {
                    expanded = expanded.concat(await found.expand(dictionary));
                }
                else throw new Error(`unknown word [${word}]`);
            }
        };
        var commentFetcher = {
            async fetch(word:string) {
                if (word==')') return scriptFetcher;
            }
        }
        var wordDefFetcher:{wordName:string|null,def:string[],fetch:(word:string)=>Promise<any>,end:()=>void} = {
            wordName:null,
            def:[],
            async fetch(word:string) {
                if (word==';') return scriptFetcher;
                if (!this.wordName) {
                    this.wordName=word;
                    return;
                }
                this.def.push(word);
            },
            async end() {
                loops[this.wordName as string] = new Script(this.def.join(' '));
            }
        }

        var inComment:boolean= false;
        var state:any = scriptFetcher;
        for (var word of words) {
            var newState = await state.fetch(word);
            if (newState) {
                await (state.end||noop).bind(state)();
                state = newState;
            }
        }
        return expanded;
    }
}
export {Script};